const path = require('path');
const fs = require('fs');
const Module = require('./module');
const MagicString = require('magic-string');
const { has, keys, replaceIdentifiers } = require('./utils');
class Bundle {
  constructor({entry}) {
    this.entryPath = path.resolve(entry); // 得到入口文件的绝对路径
    this.modules = {}; // 存放着本次打包的所有模块
  }
  // 负责编译入口文件，然后把结果写入 outputFile
  build(outputFile) {
    let entryMoudle = (this.entryMoudle = this.fetchModule(this.entryPath));
    this.statements = entryMoudle.expandAllStatements();
    this.deConflict(); // 解决变量名冲突的问题
    const transformedCode = this.generate();
    fs.writeFileSync(outputFile, transformedCode);
  }

  deConflict() {
    const defines = {};
    const conflicts = {};
    this.statements.forEach(statement => {
      keys(statement._defines).forEach(name => {
        if(has(defines,name)) { // 如果已经定义过了，说明变量名冲突了
          conflicts[name] = true; // 标记说这个变量名冲突了
        } else {
          defines[name] = [];
        }
        defines[name].push(statement._module);
      })
    });
    keys(conflicts).forEach(name => {
      const modules = defines[name];
      modules.pop(); // 最后一个不处理
      modules.forEach((module, index) => {
        const replacement = `${name}$${index+1}`;
        module.rename(name, replacement);
      });
    });
  }

  /**
   * 
   * @param {*} importee 被倒入的模块的绝对路径也可能是相对路径
   * @param {*} importer 导入的模块的绝对路径
   * @returns 
   */
  fetchModule(importee,importer){
    let route;
    if(!importer){
        route=importee;
    }else{
        if(path.isAbsolute(importee)){
            route=importee;
        }else{
            route = path.resolve(path.dirname(importer),importee);
        }
    }
    if(route){
        let code = fs.readFileSync(route,'utf8');
        const module = new Module({
            code,
            path:route,
            bundle:this
        });
        return module;
    }
  }

  generate() {
    let transformedCode = new MagicString.Bundle();
    this.statements.forEach(statement => {
      let replacements = {};
      Object.keys(statement._dependsOn).concat(statement._defines).forEach(name => {
        const canonicalName = statement._module.getCanonicalName(name);
        if(name !== canonicalName) {
          replacements[name] = canonicalName;
        }
      })
      const content = statement._source.clone();
      if(/^Export/.test(statement.type)) {
        if(statement.type === 'ExportNamedDeclaration') {
          content.remove(statement.start, statement.declaration.start);
        }
      }
      replaceIdentifiers(statement, content, replacements);
      transformedCode.addSource({
        content,
        separator: '\n',
      })
    });
    return transformedCode.toString();
  }
}

module.exports = Bundle;